﻿using ApplicationBase.Core.Context;
using IdentityService.Domain.Aggregates.RoleAggregate;
using IdentityService.Domain.Aggregates.UserAccountAggregate;
using IdentityService.Domain.Common;
using IdentityService.Domian.Repositories;
using Microsoft.Extensions.Options;

namespace IdentityService.Domain.Services
{
    public interface IUserAccountService
    {
        Task<SignUpResult> SignUpAsync(string userName, string password);
        Task<SignInResultModel> SignInAsync(string userName, string password);
        Task DeleteAsync(long id);
        Task<ModifyPasswordResult> ModifyPasswordAsync(string oldPassword, string newPassword);
        Task ModifyLockFlagAsync(long id, bool flag);
    }

    public class UserAccountService : IUserAccountService
    {
        private readonly IServiceContext _serviceContext;
        private readonly IUserAccountRepository _repository;
        private readonly IdentityOptions _options;
        public UserAccountService(IServiceContext serviceContext, 
            IUserAccountRepository repository, IOptions<IdentityOptions> options)
            => (_serviceContext, _repository, _options) 
            = (serviceContext, repository, options.Value ?? new IdentityOptions());

        public async Task<SignUpResult> SignUpAsync(string userName, string password)
        {
            if(!InspectPasswordFormat(password))
                return SignUpResult.DisallowPasswordFormat;
            
            if (await _repository.CheckUserNameExistedAsync(userName))
                return SignUpResult.UserNameExisted;

            var account = new UserAccount(_serviceContext.GenerateId(), userName, password);

            //创建默认Claim
            var idClaim = new UserAccountClaim(_serviceContext.GenerateId(), ClaimKeyDictionary.UserAccountId, account.Id.ToString());
            var userNameClaim = new UserAccountClaim(_serviceContext.GenerateId(), ClaimKeyDictionary.UserName, account.UserName);
            account.Claims.Add(idClaim);
            account.Claims.Add(userNameClaim);

            await _repository.AddAsync(account);

            return SignUpResult.Success;
        }  

        public async Task<SignInResultModel> SignInAsync(string userName, string password)
        {
            var result = new SignInResultModel();

            var user = await _repository.FindByUserNameAsync(userName);
            if (user == null)
            {
                result.Result = SignInResult.UserNameOrPasswordWrong;
                return result;
            }

            if (user.LockoutUntil > DateTime.UtcNow)
            {
                result.Result = SignInResult.AccountLockout;
                result.LockoutUntil = user.LockoutUntil;
                return result;
            }

            if(user.CheckPassword(password))
            {
                user.AccessFailedCount = 0;
                result.Id = user.Id;
                return result;
            }
            else
            {
                user.AccessFailedCount++;
                if(user.AccessFailedCount > _options.Lockout.MaxFailedAccessAttempts)
                    user.Lockout(DateTime.UtcNow.AddSeconds(_options.Lockout.LockoutTimeSpan.TotalSeconds));

                result.Result = SignInResult.UserNameOrPasswordWrong;
                return result;
            }
        }

        public async Task DeleteAsync(long id)
        {
            var account = await _repository.FindByIdIncludeAllAsync(id);
            if (account == null)
                throw new ArgumentNullException("id not found");

            _repository.Delete(account);
        }

        public async Task ModifyLockFlagAsync(long id, bool flag)
        {
            var account = await _repository.FindByIdAsync(id);
            if (account == null)
                throw new ArgumentNullException("id not found");

            if (flag)
                account.Lockout(DateTime.MaxValue);
            else
                account.UnLock();
        }

        public async Task<ModifyPasswordResult> ModifyPasswordAsync(string oldPassword, string newPassword)
        {
            var user = await _repository.FindByIdAsync(_serviceContext.GetUserId());
            if (user == null) throw new ArgumentNullException("Account with user id undfinded.");

            if (!user.CheckPassword(oldPassword))
                return ModifyPasswordResult.OldPasswordWrong;

            if (!InspectPasswordFormat(newPassword))
                return ModifyPasswordResult.NewPasswordFormatWrong;

            user.ModifyPassword(newPassword);
            return ModifyPasswordResult.Success;
        }

        private bool InspectPasswordFormat(string password)
        {
            if (password.Length < _options.Password.MinLength) return false;
            if (password.Length > _options.Password.MaxLength) return false;
            return true;
        }

        
    }

    public enum SignUpResult
    {
        Success,
        DisallowPasswordFormat,
        UserNameExisted,
    }

    public class SignInResultModel
    {
        public SignInResult Result { get; set; } = SignInResult.Success;
        public DateTime? LockoutUntil { get; set; }
        public long Id { get; set; }
    }

    public enum SignInResult
    {
        Success,
        UserNameOrPasswordWrong,
        AccountLockout
    }

    public enum ModifyPasswordResult
    {
        Success,
        OldPasswordWrong,
        NewPasswordFormatWrong
    }
}
